package net.wanderinghorse.json;

import java.io.IOException;
import java.io.InputStream;
import java.util.HashMap;
import java.util.Map;
import java.util.logging.Logger;

/**
 * A collection of utility functions for working with the JSON API.
 */
public class JSONUtil {
    private static final Logger log = Logger.getLogger(JSONUtil.class.getName());
    /**
     * Equivalent to (new JSONObject(readAll(is)).
     *
     * @param is Input stream to read JSON from.
     * @return a JSONObject holding the parsed data.
     * @throws IOException If the input API throws.
     * @throws JSONException If the underlying JSONObject API throws. That is, if the input
     * stream contains non-JSON data or is otherwise malformed.
     */
    public static JSONObject readAllAsJSON( final InputStream is ) throws IOException, JSONException {
           return new JSONObject( readAll( is ) );
    }

    /**
     * Reads the entire contents of the given input stream and returns it as a string.
     * This function does not do any sort of parsing of the data.
     *
     * @param is The input stream to consume.
     * @return The entire contents of the stream.
     * @throws IOException If the reading API throws.
     */
    public static String readAll( final InputStream is ) throws IOException {
        if( null == is ) {
            throw new IllegalArgumentException(JSONUtil.class.getName()+".readAll() was passed a null stream!");
        }
        StringBuilder sb = new StringBuilder();
        {
            /**
             WTF does the StringBuilder API not support byte
             or byte[] args? That lack makes populating it from an
             input stream a pain in the butt (and inefficient - with
             byte[] we could buffer a number of bytes per read()).
             */
            int rc = 0;
            while( (rc = is.read()) >= 0 )
            {
                sb.append( (char) rc );
            }
        }
        return sb.toString();
    }

    private static final Map<String,JSONObject> configMap = new HashMap<String, JSONObject>();

    /**
     * Tries to load a configuration object from a file named
     * ('/'+klass.getName().replace('.','/')+".json", using klass.getResourceAsStream()
     * to fetch the file. On success it returns the root JSON
     * object. On error it returns null.
     *
     * On success this request is cached, so that subsequent calls with the same
     * class get the same configuration object.
     *
     * @param klass The class to load the configuration for.
     * @return As described above.
     */
    public static JSONObject getConfigObject(final Class klass) {
        final String cname = klass.getName();
        JSONObject conf;
        synchronized (configMap) {
            conf = configMap.get(cname);
            if( null != conf ) return conf;
            try {
                final String jname = '/'+cname.replace('.','/')+".json";
                final InputStream is = klass.getResourceAsStream(jname);
                if( null != is ) {
                    conf = JSONUtil.readAllAsJSON( is );
                }
            }
            catch(Exception ex) {
                /** ignore */
                log.warning("Could not read JSON config for class "+cname+": "+ex);
            }
            if( null != conf ) {
                configMap.put(cname, conf);
            }
        }
        return conf;
    }
}
